﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace MetaSharp.Transformation.Tests.Patterns
{
	public class RuleTests
	{
		[Fact]
		public async Task ParsesSingleRuleSuccessfully()
		{
			var p = Pattern.Rule(Pattern.Value('a'));
            var m = await Pattern.Match(p, "a");
            Assert.True(m.Matched);
		}

		[Fact]
		public async Task RecursiveArrayToTreeRule()
		{
			// Tree = Tree any | any;
            var p = default(Pattern);
            p = Pattern.Rule(
                Pattern.Or(
                    Pattern.And(
                        Pattern.Reference(() => p),
                        Pattern.Any()),
                    Pattern.Any()));

            var m = await Pattern.Match(p, 1, 2, 3);
            Assert.Equal(new object[] { new object[] { 1, 2 }, 3 }, m.Value);
		}

		[Fact]
		public async Task ParsesRuleExactlyOnce()
		{
			var count = 0;
            // a = x:'a' -> { count++; return x; }
            var a = Pattern.Rule(
                Pattern.Production(
                    Pattern.Variable("x", Pattern.Value('a')),
                    (char x) =>
                    {
                        count++;
                        return x;
                    }));

            // r = (a 'z')? (a 'b')
            var p = await Pattern.Match(
                Pattern.And(
                    Pattern.Optional(Pattern.And(a, Pattern.Value('z'))),
                    Pattern.And(a, Pattern.Value('b'))),
                "ab");

            Assert.True(p.Matched);
			Assert.Equal(1, count);
		}

		[Fact]
		public async Task MatchInsideArrayIsCalledExactlyOnce()
		{
			int projectionCount = 0;
			var two = Pattern.Rule(
				Pattern.Production(
					Pattern.Variable("x", Pattern.Value(2)),
					new Func<int, int>(x =>
					{
						projectionCount++;
						return x;
					})));

            var p = Pattern.Transform(
                Pattern.And(
                    Pattern.Optional(Pattern.And(Pattern.Value(1), Pattern.Array(two), Pattern.Value(0))),
                    Pattern.And(Pattern.Value(1), Pattern.Array(two), Pattern.Value(3))));
            var m = await Pattern.Match(
                p,
				new object[] { 1, new[] { 2 }, 3 });

			Assert.True(m.Matched);
			var v0 = (object[])m.Value;
			Assert.Null(v0[0]);
			var v1 = (object[])v0[1];
			Assert.Equal(1, projectionCount);
			Assert.Equal(1, v1[0]);
			Assert.Equal(2, v1[1]);
			Assert.Equal(3, v1[2]);
		}

		[Fact]
		public async Task DirectLeftRecursionIsSuccessful()
		{
			var result = await Pattern.Match(DlrTest.exp, "1+2+3");
			Assert.True(result.Matched);
			
			var v0 = (object[])result.Value;
			var v1 = (object[])v0[0];
			Assert.Equal('1', v1[0]);
			Assert.Equal('+', v1[1]);
			Assert.Equal('2', v1[2]);
			Assert.Equal('+', v0[1]);
			Assert.Equal('3', v0[2]);
		}

		class DlrTest
		{
			//num = 0..9;
			//exp = exp '+' num | num;
			private static readonly Pattern num = Pattern.Rule(Pattern.Range('0', '9'));
			public static readonly Pattern exp = Pattern.Rule(
				Pattern.Or(
					Pattern.And(
                        Pattern.Reference(() => exp),
						Pattern.Value('+'),
						num),
					num));
		}

		[Fact]
		public void IndirectLeftRecursionFails()
		{
            Pattern.Match(IlrTest.exp, "111").WaitForThrow<TransformationException>();
        }

		class IlrTest
		{
			//num = exp;
			//exp = num;
			private static readonly Pattern num = Pattern.Rule(Pattern.Reference(() => exp));
			public static readonly Pattern exp = Pattern.Rule(Pattern.Reference(() => num));
		}
	}
}
